package org.example.repository.dialect.mysql;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.example.repository.*;
import org.example.repository.condition.BinaryOperatorCondition;
import org.example.repository.condition.ConjectionCondition;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Getter
@Setter
@Accessors(fluent = true)
class SqlBuilder {

    private final StringBuilder builder = new StringBuilder(200);
    private final List<Object> valueList = new ArrayList<>();

    private Schema schema;
    private Query query;
    private boolean asTemplate = true;
    private Map<String, Object> entity;

    public SqlBuilder(Schema schema) {
        this.schema = schema;
    }

    public SqlStatement buildSelectStatement() {
        builder.append("select * from ");
        builder.append(identity(schema.name()));
        if (query != null) {
            if (query.where() != null) {
                appendWhereClause();
            }
            if (query.limit() != null) {
                builder.append(" limit ");
                builder.append(query.limit());
            }
            if (query.offset() != null) {
                builder.append(" offset ");
                builder.append(query.offset());
            }
        }
        return getStatement();
    }

    public SqlStatement buildCountStatement() {
        builder.append("select count(*) from ");
        builder.append(identity(schema.name()));
        if (query != null) {
            if (query.where() != null) {
                appendWhereClause();
            }
        }
        return getStatement();
    }

    public SqlStatement buildCreateTableStatement() {
        builder.append("create table if not exists ");
        builder.append(identity(schema.name()));
        builder.append(" (\n    ");
        int i = 0;
        for (Property property : schema.properties()) {
            if (i > 0) builder.append(",\n    ");
            builder.append(identity(property.name()));
            builder.append(" ");
            builder.append(getDbType(property.type()));
            if (property.autoIncrement()) {
                builder.append(" auto_increment");
            }
            if (property.primaryKey()) {
                builder.append(" primary key");
            }
            i++;
        }
        builder.append("\n)");
        return getStatement();
    }

    public SqlStatement buildInsertStatement() {
        builder.append("insert into ");
        builder.append(identity(schema.name()));
        builder.append(" (");
        int i = 0;
        for (Property property : schema.properties()) {
            String name = property.name();
            if (entity.containsKey(name)) {
                if (i > 0) builder.append(", ");
                builder.append(identity(name));
                i++;
            }
        }
        builder.append(") values (");
        i = 0;
        for (Property property : schema.properties()) {
            String name = property.name();
            if (entity.containsKey(name)) {
                if (i > 0) builder.append(", ");
                appendValue(entity.get(name));
                i++;
            }
        }
        builder.append(")");
        return getStatement();
    }

    public SqlStatement buildUpdateStatement() {
        builder.append("update ");
        builder.append(identity(schema.name()));
        builder.append(" set ");
        int i = 0;
        for (Property property : schema.properties()) {
            String name = property.name();
            if (entity.containsKey(name)) {
                if (i > 0) builder.append(", ");
                builder.append(identity(name));
                builder.append(" = ");
                appendValue(entity.get(name));
                i++;
            }
        }
        if (query.where() != null) {
            appendWhereClause();
        }
        return getStatement();
    }

    public SqlStatement buildClearStatement() {
        builder.append("truncate table ");
        builder.append(identity(schema.name()));
        return getStatement();
    }

    private String getDbType(String type) {
        return switch (type) {
            case DataTypes.String -> "varchar(255)";
            case DataTypes.Text -> "text";
            case DataTypes.Boolean -> "bit";
            case DataTypes.Long -> "long";
            case DataTypes.Int -> "int";
            default -> throw new UnsupportedOperationException("not supported type " + type);
        };
    }

    private void appendWhereClause() {
        builder.append(" where ");
        Condition condition = query.where();
        if (condition instanceof ConjectionCondition cc) {
            int i = 0;
            for (Condition sub : cc.getChildren()) {
                if (i > 0) {
                    builder.append(" ");
                    builder.append(cc.getType());
                    builder.append(" ");
                }
                if (sub instanceof BinaryOperatorCondition boc) {
                    builder.append(identity(boc.getProperty()));
                    builder.append(" ");
                    builder.append(boc.getOp());
                    builder.append(" ");
                    appendValue(boc.getValue());
                }
                i++;
            }
        } else if (condition instanceof BinaryOperatorCondition boc) {
            builder.append(identity(boc.getProperty()));
            builder.append(" ");
            builder.append(boc.getOp());
            builder.append(" ");
            appendValue(boc.getValue());
        }
    }

    private void appendValue(Object value) {
        if (asTemplate) {
            builder.append("?");
            valueList.add(value);
        } else {
            builder.append(escape(value));
        }
    }

    private SqlStatement getStatement() {
        String sql = builder.toString();
        Object[] values = valueList.toArray();
        return new SqlStatement(sql, values);
    }

    private static String escape(Object value) {
        if (value instanceof Integer i) {
            return String.valueOf(i);
        } else if (value instanceof String s) {
            return "'" + s.replaceAll("'", "''") + "'";
        } else {
            throw new UnsupportedOperationException("no converter for value type " + value.getClass().getName());
        }
    }

    private static String identity(String name) {
        return "`" + name + "`";
    }
}
